#include "nwudpclient.h"
#include <chrono>
#include <pthread.h>

namespace NetworkUtil {


UdpClient::UdpClient(const ConnectionConfigInfo& stConnCfgInfo)
    : BaseConnection(stConnCfgInfo)
{

}

UdpClient::~UdpClient()
{
    m_pfnRecvCallback = nullptr;
}

void UdpClient::setRecvCallbackFunc(std::function<void(const std::string& strDataBuf)> pfnRecvCallback)
{
    m_pfnRecvCallback = pfnRecvCallback;
    return;
}

void UdpClient::init()
{
    if (m_bInited)
    {
        return;
    }

    try {
        if (!(m_sp4ObjIOCtx.get()))
        {
            m_sp4ObjIOCtx = std::make_shared<asio::io_context>();
        }

        if (!(m_sp4ObjSocket.get()))
        {
            m_sp4ObjSocket = std::make_shared<asio::ip::udp::socket>(*(m_sp4ObjIOCtx.get()), asio::ip::udp::endpoint(asio::ip::udp::v4(), m_stConnCfgInfo.ui16LocalPort));
        }

        asio::ip::udp::resolver resolver(*(m_sp4ObjIOCtx.get()));
        m_objSrvEndPoints = resolver.resolve(asio::ip::udp::v4()
                                             , m_stConnCfgInfo.strSrvIP
                                             , std::to_string(m_stConnCfgInfo.ui16SrvPort));

        m_sp4ObjIOCtx->run();
        m_bConnected = true;
        m_bRunning = true;
        if (!(m_sp4thRecv.get()))
        {
            m_sp4thRecv = std::make_shared<std::thread> (&UdpClient::onRecvDataProcess, this);
        }

        m_bInited = true;

    } catch (/*...*/std::exception& e) {
        printf("func [UdpClient::init], connect to server (%s:%d) failed, error info: %s.\r\n" \
               , m_stConnCfgInfo.strSrvIP.data(), m_stConnCfgInfo.ui16SrvPort, e.what());

        m_bInited = true;
        deinit();
    }

    return;
}

void UdpClient::deinit()
{
    if (m_bInited)
    {
        m_bConnected = false;
        m_bRunning = false;

        try {
            if (m_sp4ObjSocket.get())
            {
                m_sp4ObjSocket->shutdown(asio::ip::udp::socket::shutdown_both);
                m_sp4ObjSocket->release();
                m_sp4ObjSocket->close();
            }

            if (m_sp4ObjIOCtx.get())
            {
                m_sp4ObjIOCtx->stop();
            }

            if (m_sp4thRecv.get())
            {
                pthread_cancel(m_sp4thRecv->native_handle()); // 因为read会阻塞线程，所以需要强行杀死这个线程
                if ( m_sp4thRecv->joinable()) {
                    m_sp4thRecv->join();
                }
            }
        } catch (...) {
            // catch some exception
        }

        if (m_sp4thRecv.get())
        {
            m_sp4thRecv.reset();
            m_sp4thRecv = nullptr;
        }

        if (m_sp4ObjSocket.get())
        {
            m_sp4ObjSocket.reset();
            m_sp4ObjSocket = nullptr;
        }

        if (m_sp4ObjIOCtx.get())
        {
            m_sp4ObjIOCtx.reset();
            m_sp4ObjIOCtx = nullptr;
        }

        m_bInited = false;
    }

    return;
}

uint32_t UdpClient::read(char* pDataBuf, uint32_t ui32DataBufLen)
{
    uint32_t ui32Ret = 0;

    try {
        if (m_sp4ObjSocket.get() && pDataBuf)
        {
            std::size_t s32Ret = m_sp4ObjSocket->receive_from(asio::buffer(pDataBuf, static_cast<std::size_t>(ui32DataBufLen))
                                                              , m_objSenderEndPoint);
            ui32Ret = static_cast<uint32_t>(s32Ret);
        }
    } catch (std::exception& e) {
        std::cerr << "func [UdpClient::read], exception info: " << e.what() << std::endl;
        m_bConnected = false;
    }

    return ui32Ret;
}

uint32_t UdpClient::read(std::string& strDataBuf)
{
#if 0
    uint32_t ui32Ret = 0;

    try {
        if (m_sp4ObjSocket.get())
        {
            std::size_t s32Ret = m_sp4ObjSocket->receive_from(asio::buffer(strDataBuf)
                                                              , m_objSenderEndPoint);
            ui32Ret = static_cast<uint32_t>(s32Ret);
        }
    } catch (std::exception& e) {
        std::cerr << "func [UdpClient::read], exception info: " << e.what() << std::endl;
        m_bConnected = false;
    }

    return ui32Ret;
#else
    char *pBuffer = new (std::nothrow) char[m_stConnCfgInfo.ui32BufMaxSize];
    if (!pBuffer)
    {
        return 0;
    }

    memset(pBuffer, 0, m_stConnCfgInfo.ui32BufMaxSize);
    uint32_t ui32Ret = read(pBuffer, m_stConnCfgInfo.ui32BufMaxSize);
    if (0 < ui32Ret)
    {
        strDataBuf = std::string(pBuffer, ui32Ret);
    }

    if (pBuffer)
    {
        delete[] pBuffer;
        pBuffer = nullptr;
    }

    return ui32Ret;
#endif
}

uint32_t UdpClient::write(const char* pDataBuf, uint32_t ui32DataBufLen)
{
    uint32_t ui32Ret = 0;

    try {
        if (m_sp4ObjSocket.get() && pDataBuf)
        {
            if (m_stConnCfgInfo.bAsync)
            {
                std::error_code errCode;
                std::size_t sendByte;

                m_sp4ObjSocket->async_send_to(asio::buffer(pDataBuf, static_cast<std::size_t>(ui32DataBufLen))
                                              , *m_objSrvEndPoints.begin()
                                              , [&errCode, &sendByte, this](std::error_code ec, std::size_t bytes_sent)
                {
                    (void)this;
                    errCode = ec;
                    sendByte = bytes_sent;
                    return;
                });
            }
            else
            {
                std::size_t s32Ret = m_sp4ObjSocket->send_to(asio::buffer(pDataBuf, static_cast<std::size_t>(ui32DataBufLen))
                                                             , *m_objSrvEndPoints.begin());

                ui32Ret = static_cast<uint32_t>(s32Ret);
            }
        }
    } catch (std::exception& e) {
        std::cerr << "func [UdpClient::write], exception info: " << e.what() << std::endl;
        m_bConnected = false;
    }

    return ui32Ret;
}

uint32_t UdpClient::write(const std::string& strDataBuf)
{
    return write(strDataBuf.data(), static_cast<uint32_t>(strDataBuf.size()));
}

std::string UdpClient::remoteInfo()
{
    return BaseConnection::remoteInfo(m_objSenderEndPoint);
}

void UdpClient::onRecvDataProcess()
{
    while (m_bRunning)
    {
        if (m_bConnected)
        {
            std::string strDataInfo = "";
            read(strDataInfo);

            if (!(strDataInfo.empty()))
            {
                if (m_pfnRecvCallback)
                {
                    m_pfnRecvCallback(strDataInfo);
                }
            }
        }

        std::this_thread::yield();
    }

    return;
}


} // namespace NetworkUtil

